package de.openkanban.client.ui.board;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeSet;

import com.google.gwt.http.client.RequestCallback;
import com.smartgwt.client.types.Alignment;
import com.smartgwt.client.types.Overflow;
import com.smartgwt.client.util.BooleanCallback;
import com.smartgwt.client.util.EventHandler;
import com.smartgwt.client.util.SC;
import com.smartgwt.client.widgets.Canvas;
import com.smartgwt.client.widgets.IButton;
import com.smartgwt.client.widgets.Label;
import com.smartgwt.client.widgets.events.ClickEvent;
import com.smartgwt.client.widgets.events.ClickHandler;
import com.smartgwt.client.widgets.events.DoubleClickEvent;
import com.smartgwt.client.widgets.events.DoubleClickHandler;
import com.smartgwt.client.widgets.events.DropEvent;
import com.smartgwt.client.widgets.events.DropHandler;
import com.smartgwt.client.widgets.form.DynamicForm;
import com.smartgwt.client.widgets.form.fields.TextItem;
import com.smartgwt.client.widgets.form.fields.events.BlurEvent;
import com.smartgwt.client.widgets.form.fields.events.BlurHandler;
import com.smartgwt.client.widgets.form.fields.events.FocusEvent;
import com.smartgwt.client.widgets.form.fields.events.FocusHandler;
import com.smartgwt.client.widgets.form.fields.events.KeyPressEvent;
import com.smartgwt.client.widgets.form.fields.events.KeyPressHandler;
import com.smartgwt.client.widgets.layout.HLayout;
import com.smartgwt.client.widgets.layout.Layout;
import com.smartgwt.client.widgets.layout.VLayout;

import de.openkanban.client.ui.presenter.CreateActivityRequestCallback;
import de.openkanban.client.ui.presenter.GetActivityByIdRequestCallback;
import de.openkanban.client.ui.presenter.GetKanbansByActivityIdRequestCallback;
import de.openkanban.client.ui.presenter.MoveKanbanToActivityRequestCallback;
import de.openkanban.client.ui.presenter.PreparedRequest;
import de.openkanban.client.ui.presenter.RemoveActivityRequestCallback;
import de.openkanban.client.ui.presenter.UpdateActivityRequestCallback;
import de.openkanban.client.util.RootPanel;
import de.openkanban.client.util.StringUtils;
import de.openkanban.shared.dto.ActivityDto;
import de.openkanban.shared.dto.KanbanDto;

/**
 * GUI Element for the ActivityView
 */
public class ActivityView extends VLayout {

	public static final String DROPTYPE_INACTIVE = "inactive_activity";
	public static final String DROPTYPE_ACTIVE = "active_activity";
	private static final String DEFAULT_ACTIVITY_NAME_TEXT = "activity-name";
	private static final String DEFAULT_WIP_LIMIT_TEXT = "wip-limit";
	private static final int HEADER_HEIGHT = 24;
	private static final int MIN_ACTIVITY_WIDTH = 200;
	private static final int WIP_LIMIT_WIDTH = 60;

	private IButton cancelButton;
	private IButton deleteButton;

	private Label activityNameLabel;
	private DynamicForm activityNameForm;
	private TextItem activityNameTextItem;

	private Label activityWipLimitLabel;
	private DynamicForm activityWipLimitForm;
	private TextItem activityWipLimitTextItem;

	private HLayout activityHeaderHLayout;
	private VLayout kanbanContainerVLayout;

	private Long parentBoardId;
	private ActivityDto activityDto;

	private Map<Long, KanbanView> kanbanViewMap;
	private boolean canAddKanban;
	private MoveKanbanToActivityRequestCallback moveKanbanToActivityRequestCallback;
	private int dragDropIndex;

	public ActivityView(Long parentBoardId) {
		this.parentBoardId = parentBoardId;

		kanbanViewMap = new HashMap<Long, KanbanView>();

		moveKanbanToActivityRequestCallback = new MoveKanbanToActivityRequestCallback();

		// components
		cancelButton = new BoardHeaderButton("cancel");
		cancelButton.setAutoFit(true);
		cancelButton.addClickHandler(cancelButtonClickHandler(this));

		deleteButton = new BoardHeaderButton("x");
		deleteButton.setAutoFit(true);
		deleteButton.setVisible(false);
		deleteButton.addClickHandler(deleteButtonClickHandler(this));

		activityNameLabel = new Label();
		activityNameLabel.setStyleName("labelActivity");
		activityNameLabel.setHeight(HEADER_HEIGHT + 10);
		activityNameLabel.setWidth("*");
		activityNameLabel.setCanDropBefore(false);
		activityNameLabel.addDoubleClickHandler(activityNameLabelDoubleClickHandler());

		activityNameForm = new DynamicForm();

		activityNameTextItem = new TextItem();
		activityNameTextItem.setShowTitle(false);
		activityNameTextItem.setValue(DEFAULT_ACTIVITY_NAME_TEXT);
		activityNameTextItem.setHeight(HEADER_HEIGHT - 2);
		activityNameTextItem.setShowFocused(true);
		activityNameTextItem.setTextBoxStyle("labelActivity");
		activityNameTextItem.setAlign(Alignment.CENTER);
		activityNameTextItem.addKeyPressHandler(activityTextItemKeyPressHandler(this, activityNameTextItem, activityNameLabel,
				activityNameForm, 0));
		activityNameTextItem.addBlurHandler(activityTextItemBlurHandler(this, activityNameTextItem, activityNameLabel,
				activityNameForm, 0));
		activityNameTextItem.addFocusHandler(new FocusHandler() {
			@Override
			public void onFocus(FocusEvent event) {
				if (DEFAULT_ACTIVITY_NAME_TEXT.equals(activityNameTextItem.getValueAsString())) {
					activityNameTextItem.clearValue();
				}
			}
		});

		activityNameForm.setHeight(HEADER_HEIGHT);
		activityNameForm.setFields(activityNameTextItem);

		activityWipLimitLabel = new Label();
		activityWipLimitLabel.setShowFocused(true);
		activityWipLimitLabel.setStyleName("labelActivity");
		activityWipLimitLabel.setHeight(HEADER_HEIGHT + 10);
		activityWipLimitLabel.setWidth(WIP_LIMIT_WIDTH);
		activityWipLimitLabel.addDoubleClickHandler(activityWipLimitLabelDoubleClickHandler());

		activityWipLimitForm = new DynamicForm();

		activityWipLimitTextItem = new TextItem();
		activityWipLimitTextItem.setShowTitle(false);
		activityWipLimitTextItem.setValue(DEFAULT_WIP_LIMIT_TEXT);
		activityWipLimitTextItem.setWidth(WIP_LIMIT_WIDTH);
		activityWipLimitTextItem.setHeight(HEADER_HEIGHT - 2);
		activityWipLimitTextItem.setTextBoxStyle("labelActivity");
		activityWipLimitTextItem.addKeyPressHandler(activityTextItemKeyPressHandler(this, activityWipLimitTextItem,
				activityWipLimitLabel, activityWipLimitForm, 2));
		activityWipLimitTextItem.addBlurHandler(activityTextItemBlurHandler(this, activityWipLimitTextItem, activityWipLimitLabel,
				activityWipLimitForm, 2));
		activityWipLimitTextItem.addFocusHandler(new FocusHandler() {
			@Override
			public void onFocus(FocusEvent event) {
				if (DEFAULT_WIP_LIMIT_TEXT.equals(activityWipLimitTextItem.getValueAsString())) {
					activityWipLimitTextItem.clearValue();
				}
			}
		});

		activityWipLimitForm.setWidth(WIP_LIMIT_WIDTH);
		activityWipLimitForm.setHeight(HEADER_HEIGHT);
		activityWipLimitForm.setFields(activityWipLimitTextItem);

		activityHeaderHLayout = new HLayout();
		activityHeaderHLayout.setHeight(HEADER_HEIGHT);
		activityHeaderHLayout.setStyleName("activityHeaderHLayout");

		Layout placeHolderLayout = new Layout();
		placeHolderLayout.setWidth("*");
		activityHeaderHLayout.setCanDropBefore(false);
		activityHeaderHLayout.addMember(activityNameForm);
		activityHeaderHLayout.addMember(placeHolderLayout);
		activityHeaderHLayout.addMember(activityWipLimitForm);
		activityHeaderHLayout.addMember(cancelButton);
		activityHeaderHLayout.addMember(deleteButton);

		kanbanContainerVLayout = new VLayout();
		kanbanContainerVLayout.setScrollbarSize(9);
		kanbanContainerVLayout.setOverflow(Overflow.AUTO);
		kanbanContainerVLayout.setDropTypes("kanban");
		kanbanContainerVLayout.setCanAcceptDrop(true);
		kanbanContainerVLayout.setAnimateMembers(true);
		kanbanContainerVLayout.setShowDragPlaceHolder(false);

		// activity events
		kanbanContainerVLayout.addDropHandler(new DropHandler() {
			@Override
			public void onDrop(DropEvent event) {
				KanbanView target = (KanbanView) EventHandler.getDragTarget();
				Long sourceActivityId = target.getParentActivityId();
				Long targetActivityId = activityDto.getActivityId();

				int dragDiff = getDragDifference(sourceActivityId, targetActivityId);

				boolean dragOK = true;

				if (isInvalidTarget(dragDiff)) {
					dragOK = false;
					event.cancel();
				} else if (isForwardDirection(dragDiff) && !target.isReadyForPull()) {
					dragOK = false;
					event.cancel();
				} else if (activityLimitAcceeded()) {
					dragOK = false;
					event.cancel();
					SC.say("Kanban nicht verschoben werden: Work-In-Progress Limit erreicht.");
				}

				if (dragOK) {
					target.setParentActivityId(activityDto.getActivityId());
					target.getKanbanDto().setPullable(!target.getKanbanDto().getPullable());
					if (isFirstActivityView()) {
						target.getKanbanDto().setPullable(true);
					} else {
						target.getKanbanDto().setPullable(false);
					}
					target.togglePullableButton();

					ActivityView sourceActivityView = RootPanel.main.getBoard().getWhiteBoardView()
							.getActivityViewById(sourceActivityId);
					sourceActivityView.getKanbanViewMap().remove(target.getKanbanDto().getKanbanId());
					kanbanViewMap.put(target.getKanbanDto().getKanbanId(), target);

					PreparedRequest prepReq = new PreparedRequest("activity", activityDto.getActivityId() + "/kanban/"
							+ target.getTitle() + "/move");

					prepReq.sendAsyncRequest(moveKanbanToActivityRequestCallback);
				}
			}

			private boolean activityLimitAcceeded() {
				return activityDto.getWipLimit() <= kanbanContainerVLayout.getMembers().length;
			}

			private boolean isInvalidTarget(int dragDiff) {
				return dragDiff > 1 || dragDiff == 0;
			}

			private boolean isForwardDirection(int dragDiff) {
				return dragDiff == 1;
			}
		});

		// activity properties
		setMargin(5);
		setStyleName("activity");
		setWidth("*");
		setMinWidth(MIN_ACTIVITY_WIDTH);
		setHeight100();

		// drop line style
		Canvas dropLineProperties = new Canvas();
		dropLineProperties.setBackgroundColor("#99ddee");
		kanbanContainerVLayout.setDropLineProperties(dropLineProperties);
		kanbanContainerVLayout.setDropLineThickness(1);

		// add members
		addMember(activityHeaderHLayout);
		addMember(kanbanContainerVLayout);
	}

	public ActivityView(ActivityDto activityDto, Long parentBoardId) {
		this(parentBoardId);
		this.activityDto = activityDto;
		this.activityNameLabel.setContents(activityDto.getActivityName());
		this.activityWipLimitLabel.setContents(Integer.toString(activityDto.getWipLimit()));

		hideEditActivityName();
		hideEditWipLimit();
		hideCancelButton();
		showDeleteButton();

		loadAllKanbans();
	}

	public void loadAllKanbans() {
		PreparedRequest prepReq = new PreparedRequest("activity/" + activityDto.getActivityId() + "/kanban", "all");
		prepReq.sendAsyncRequest(new GetKanbansByActivityIdRequestCallback(this));
	}

	public KanbanView addKanban(KanbanDto kanbanDto) {
		if (!kanbanViewExists(kanbanDto.getKanbanId())) {

			KanbanView kanbanView = new KanbanView(kanbanDto, activityDto.getActivityId());
			kanbanViewMap.put(kanbanDto.getKanbanId(), kanbanView);
			this.kanbanContainerVLayout.addMember(kanbanView);
			return kanbanView;
		}
		return null;
	}

	private int getDragDifference(Long sourceActivityId, Long targetActivityId) {
		List<ActivityView> activityViewList = RootPanel.main.getBoard().getWhiteBoardView().getActivityViewList();

		TreeSet<Long> activityIdSet = new TreeSet<Long>();
		for (ActivityView activityView : activityViewList) {
			activityIdSet.add(activityView.getActivityDto().getActivityId());
		}

		Long[] activityIdArray = activityIdSet.toArray(new Long[] {});

		int sourceIndex = 0;

		for (int i = 0; i < activityIdArray.length; i++) {
			Long id = activityIdArray[i];
			if (id.equals(sourceActivityId)) {
				sourceIndex = i;
			}
		}

		int targetIndex = 0;

		for (int i = 0; i < activityIdArray.length; i++) {
			Long id = activityIdArray[i];
			if (id.equals(targetActivityId)) {
				targetIndex = i;
			}
		}

		return targetIndex - sourceIndex;
	}

	public boolean isFirstActivityView() {
		return activityDto.getActivityId() == RootPanel.main.getBoard().getWhiteBoardView().getFirstActicityDto().getActivityId();
	}

	private void showEditActivityName() {
		activityHeaderHLayout.removeMember(activityNameLabel);
		activityHeaderHLayout.addMember(activityNameForm, 0);
		activityNameTextItem.focusInItem();
	}

	private void hideEditActivityName() {
		activityHeaderHLayout.removeMember(activityNameForm);
		activityHeaderHLayout.addMember(activityNameLabel, 0);
	}

	private void showEditWipLimit() {
		activityHeaderHLayout.removeMember(activityWipLimitLabel);
		activityHeaderHLayout.addMember(activityWipLimitForm, 2);
		activityWipLimitTextItem.focusInItem();
	}

	private void hideEditWipLimit() {
		activityHeaderHLayout.removeMember(activityWipLimitForm);
		activityHeaderHLayout.addMember(activityWipLimitLabel, 2);
	}

	public void hideCancelButton() {
		if (activityHeaderHLayout.contains(cancelButton)) {
			activityHeaderHLayout.removeMember(cancelButton);
		}
	}

	public void showDeleteButton() {
		deleteButton.setVisible(true);
	}

	private ClickHandler cancelButtonClickHandler(final ActivityView activityView) {
		return new ClickHandler() {
			@Override
			public void onClick(ClickEvent event) {
				RootPanel.main.getBoard().enableNewActivityButton();
				RootPanel.main.getBoard().getWhiteBoardView().removeActivity(activityView);
			}
		};
	}

	private ClickHandler deleteButtonClickHandler(final ActivityView activityView) {
		return new ClickHandler() {
			@Override
			public void onClick(ClickEvent event) {
				SC.confirm("Do you want to remove the activity \"" + activityView.getActivityDto().getActivityName()
						+ "\" from Board?", new BooleanCallback() {
					@Override
					public void execute(Boolean value) {
						if (value != null && value) {
							Long activityId = activityView.getActivityDto().getActivityId();
							PreparedRequest prepReq = new PreparedRequest("activity", activityId.toString()
									+ "/remove");
							prepReq.sendAsyncRequest(new RemoveActivityRequestCallback(activityView));
						}
					}
				});
			}
		};
	}

	private DoubleClickHandler activityNameLabelDoubleClickHandler() {
		return new DoubleClickHandler() {
			@Override
			public void onDoubleClick(DoubleClickEvent event) {
				activityNameTextItem.setValue(activityNameLabel.getContents());

				showEditActivityName();
			}
		};
	}

	private DoubleClickHandler activityWipLimitLabelDoubleClickHandler() {
		return new DoubleClickHandler() {
			@Override
			public void onDoubleClick(DoubleClickEvent event) {
				activityWipLimitTextItem.setValue(activityWipLimitLabel.getContents());
				showEditWipLimit();
			}
		};
	}

	private KeyPressHandler activityTextItemKeyPressHandler(final ActivityView activityView, final TextItem textItem,
			final Label label, final DynamicForm form, final int position) {
		return new KeyPressHandler() {
			@Override
			public void onKeyPress(KeyPressEvent event) {
				if ("Enter".equals(event.getKeyName())) {
					textItem.blurItem();
				}
			}
		};
	}

	private BlurHandler activityTextItemBlurHandler(final ActivityView activityView, final TextItem textItem, final Label label,
			final DynamicForm form, final int position) {
		return new BlurHandler() {
			@Override
			public void onBlur(BlurEvent event) {
				updateActivityData(activityView, textItem, label, form, position);
			}
		};
	}

	public void updateActivityDto() {
		PreparedRequest prepReq = new PreparedRequest("activity", activityDto.getActivityId().toString());
		prepReq.sendAsyncRequest(new GetActivityByIdRequestCallback(this));
	}

	private void updateActivityData(ActivityView activityView, TextItem textItem, Label label, DynamicForm form, int position) {
		label.setContents(textItem.getValueAsString());

		if (activityView.getActivityHeaderHLayout().contains(form)) {
			activityView.getActivityHeaderHLayout().removeMember(form);
			activityView.getActivityHeaderHLayout().addMember(label, position);
		}

		PreparedRequest prepReq = null;
		RequestCallback requestCallback = null;
		String context = "";
		String ressource = "";
		if (activityView.getActivityDto() != null) {
			context = "activity";
			ressource = "update";
			requestCallback = new UpdateActivityRequestCallback(activityView);
		} else {
			activityView.setActivityDto(new ActivityDto());
			context = "board";
			ressource = activityView.getParentBoardId().toString() + "/activity/add";
			requestCallback = new CreateActivityRequestCallback(activityView);
		}
		String wipLimitString = activityView.getActivityWipLimitTextItem().getValueAsString();
		String activityName = activityView.getActivityNameTextItem().getValueAsString();

		activityView.getActivityDto().setActivityName(activityName);
		if (StringUtils.isNumeric(wipLimitString)) {
			activityView.getActivityDto().setWipLimit(Integer.parseInt(wipLimitString));
		}

		prepReq = new PreparedRequest(context, ressource, activityView.getActivityDto());
		prepReq.sendAsyncRequest(requestCallback);
	}

	public void removeKanbanView(Long kanbanId) {
		kanbanContainerVLayout.removeMember(kanbanViewMap.get(kanbanId));
		kanbanViewMap.remove(kanbanId);
	}

	public boolean kanbanViewExists(Long kanbanId) {
		return kanbanViewMap.containsKey(kanbanId);
	}

	public boolean canAddKanban() {
		return this.canAddKanban;
	}

	public VLayout getKanbanContainerVLayout() {
		return kanbanContainerVLayout;
	}

	public ActivityDto getActivityDto() {
		return activityDto;
	}

	public void setActivityDto(ActivityDto activityDto) {
		this.activityDto = activityDto;
		activityNameLabel.setContents(activityDto.getActivityName());
		activityWipLimitLabel.setContents(Integer.toString(activityDto.getWipLimit()));
	}

	public Label getActivityNameLabel() {
		return activityNameLabel;
	}

	public TextItem getActivityNameTextItem() {
		return activityNameTextItem;
	}

	public HLayout getActivityHeaderHLayout() {
		return activityHeaderHLayout;
	}

	public DynamicForm getActivityNameForm() {
		return activityNameForm;
	}

	public Long getParentBoardId() {
		return parentBoardId;
	}

	public TextItem getActivityWipLimitTextItem() {
		return activityWipLimitTextItem;
	}

	public int getDragDropIndex() {
		return dragDropIndex;
	}

	public void setDragDropIndex(int dragDropIndex) {
		this.dragDropIndex = dragDropIndex;
	}

	public Map<Long, KanbanView> getKanbanViewMap() {
		return kanbanViewMap;
	}

}